/**************************************************************************************************
	$Header: /pub/cvsroot/mydns/src/mydns/main.c,v 1.15 2002/07/17 10:24:13 bboy Exp $

	Copyright (C) 2002  Don Moore <bboy@bboy.net>

	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at Your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
**************************************************************************************************/

#include "named.h"

#if MTRACE_ENABLED && HAVE_MCHECK_H
#	include <mcheck.h>
#endif

MYSQL	*mysql;														/* MySQL database connection */
QUEUE *Tasks;														/* Task queue */
time_t current_time;												/* Current time */
int exit_after_one;												/* Answer one query, then exit? */

static int time_to_die;											/* Have we received a fatal signal? */
static int multicpu;												/* If multi-CPU, number of CPUs */
static pid_t *pidlist;											/* List of related PIDs */
static int is_master;											/* Is the current process the master? */

SERVERSTATUS Status;												/* Server status information */

extern void import(char *);									/* from import.c */

extern int	opt_daemon;
extern char	*opt_conf;
extern uid_t perms_uid;
extern gid_t perms_gid;


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	USAGE
	Display program usage information.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static void
usage(status)
	int status;
{
	if (status != EXIT_SUCCESS)
	{
		fprintf(stderr, _("Try `%s --help' for more information."), progname);
		fputs("\n", stderr);
	}
	else
	{
		printf(_("Usage: %s [OPTION]..."), progname);
		puts("");
		puts(_("Listen for and respond to Internet domain name queries."));
		puts("");
/*		puts("----------------------------------------------------------------------------78");  */
		puts(_("  -b, --background        run as a daemon (move process into background)"));
		puts(_("  -c, --conf=FILE         read config from FILE instead of the default"));
#if DEBUG
		puts(_("  -d, --debug             enable debug output"));
#endif
		puts(_("  -h, --host=HOST         connect to MySQL server at HOST"));
		puts(_("  -i, --import=HOST/ZONE  import zone information from HOST for ZONE"));
		puts(_("  -p, --password=PASS     password for MySQL (or prompt from tty)"));
		puts(_("  -u, --user=USER         username for MySQL if not current user"));
		puts(_("  -v, --verbose           be more verbose while running"));
		puts(_("      --create-tables     output table creation SQL and exit"));
		puts(_("      --dump-config       output configuration and exit"));
		puts(_("      --help              display this help and exit"));
		puts(_("      --version           output version information and exit"));
		puts("");
		printf(_("The %s homepage is at %s\n"), PACKAGE_NAME, PACKAGE_HOMEPAGE);
		puts("");
		printf(_("Report bugs to <%s>.\n"), PACKAGE_BUGREPORT);
	}
	exit(status);
}
/*--- usage() -----------------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	CMDLINE
	Process command line options.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static void
cmdline(argc, argv)
	int argc;
	char *argv[];
{
	char	*optstr, *want_import = NULL;
	int	want_dump_config = 0, optc, optindex;
	struct option const longopts[] =
	{
		{"background",		no_argument,			NULL,	'b'},
		{"exit-after-one",no_argument,			NULL,	'1'},
		{"conf",				required_argument,	NULL,	'c'},
		{"debug",			no_argument,			NULL,	'd'},
		{"host",				required_argument,	NULL,	'h'},
		{"import",			required_argument,	NULL,	'i'},
		{"password",		optional_argument,	NULL,	'p'},
		{"user",				required_argument,	NULL,	'u'},
		{"verbose",			no_argument,			NULL,	'v'},
		{"create-tables",	no_argument,			NULL,	0},
		{"dump-config",	no_argument,			NULL,	0},
		{"help",				no_argument,			NULL,	0},
		{"version",			no_argument,			NULL,	0},

		{NULL, 0, NULL, 0}
	};

	error_init(argv[0], LOG_DAEMON);							/* Init output routines */

	optstr = getoptstr(longopts);
	while ((optc = getopt_long(argc, argv, optstr, longopts, &optindex)) != -1)
	{
		switch (optc)
		{
			case 0:
				{
					const char *opt = longopts[optindex].name;

					if (!strcmp(opt, "version"))									/* --version */
					{
						printf("%s ("PACKAGE_NAME") "PACKAGE_VERSION"\n", progname);
						puts("\n" PACKAGE_COPYRIGHT);
						puts(_("This is free software; see the source for copying conditions.  There is NO"));
						puts(_("warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."));
						exit(EXIT_SUCCESS);
					}
					else if (!strcmp(opt, "help"))								/* --help */
						usage(EXIT_SUCCESS);
					else if (!strcmp(opt, "dump-config"))						/* --dump-config */
						want_dump_config++;
					else if (!strcmp(opt, "create-tables"))					/* --create-tables */
						output_create_tables();
				}
				break;
			case '1':																	/* -1, --exit-after-one */
				exit_after_one++;
				break;
			case 'b':																	/* -b, --background */
				opt_daemon = 1;
				break;
			case 'c':																	/* -c, --conf=FILE */
				opt_conf = optarg;
				break;
			case 'd':																	/* -d, --debug */
#if DEBUG
				err_verbose = err_debug = 1;
#endif
				break;
			case 'h':																	/* -h, --host=HOST */
				conf_set(&Conf, "mysql-host", optarg);
				break;
			case 'i':																	/* -i, --import=HOST/ZONE */
				want_import = optarg;
				break;
			case 'p':																	/* -p, --password=PASS */
				if (optarg)
				{
					conf_set(&Conf, "mysql-password", optarg);
					memset(optarg, 'X', strlen(optarg));
				}
				else
					conf_set(&Conf, "mysql-password", passinput(_("Enter password")));
				break;
			case 'u':																	/* -u, --user=USER */
				conf_set(&Conf, "mysql-user", optarg);
				break;
			case 'v':																	/* -v, --verbose */
				err_verbose = 1;
				break;
			default:
				usage(EXIT_FAILURE);
		}
	}
	if (optind < argc)
		fprintf(stderr, "%s: %s\n", progname, _("Extraneous command-line arguments ignored"));

	load_config();

	if (want_import)
	{
		import(want_import);
		exit(EXIT_SUCCESS);
	}

	if (want_dump_config)
	{
		dump_config();
		exit(EXIT_SUCCESS);
	}
}
/*--- cmdline() ---------------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	BECOME_DAEMON
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static void
become_daemon(void)
{
	int pid;
	struct rlimit rl;

	if ((pid = fork()) < 0)
		Err("fork");
	if (pid)
		_exit(EXIT_SUCCESS);

	close(STDIN_FILENO);
	close(STDOUT_FILENO);
	close(STDERR_FILENO);

	setsid();

	if (getrlimit(RLIMIT_NOFILE, &rl) < 0)
		Err("getrlimit");
	rl.rlim_cur = rl.rlim_max;
	setrlimit(RLIMIT_NOFILE, &rl);

	if (getrlimit(RLIMIT_CORE, &rl) < 0)
		Err("getrlimit");
	rl.rlim_cur = rl.rlim_max;
	setrlimit(RLIMIT_CORE, &rl);
}
/*--- become_daemon() ---------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	DB_CONNECT
	Connect to the database.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
void
db_connect(void)
{
	u_char *host = conf_get(&Conf, "mysql-host");
	u_char *database = conf_get(&Conf, "database");
	u_char *password = conf_get(&Conf, "mysql-password");

	if (!(mysql = mysql_init(NULL)))
		Err(_("unable to allocate MySQL data structure"));
	if (!(mysql_real_connect(mysql, host, conf_get(&Conf, "mysql-user"),
									 password, database, 0, NULL, 0)))
		ErrSQL(mysql, _("error connecting to MySQL server at %s"), host);
	Verbose(_("connected to MySQL on %s, db \"%s\""), host, database);
}
/*--- db_connect() ------------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	CREATE_PIDFILE
	Creates the PID file.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static void
create_pidfile(void)
{
	char *name = conf_get(&Conf, "pidfile");
	FILE *fp;

	if (!(fp = fopen(name, "w")))
		Err("%s", name);
	fprintf(fp, "%u\n", getpid());
	fclose(fp);
}
/*--- create_pidfile() --------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	SIGUSR1_HANDLER
	Outputs server stats.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
void
sigusr1_handler(int dummy)
{
	if (is_master)
	{
		int n;
		for (n = 0; n < multicpu - 1; n++)
			kill(pidlist[n], SIGUSR1);
	}
	server_status();
}
/*--- sigusr1_handler() -------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	ALARM_HANDLER
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
void
alarm_handler(int dummy)
{
	static time_t next_cache_cleanup = 0;

	if (Cache && next_cache_cleanup && time(NULL) >= next_cache_cleanup)
	{
		cache_cleanup();
		next_cache_cleanup = current_time + Cache->expire;
	}
	else if (Cache && !next_cache_cleanup)
		next_cache_cleanup = time(NULL) + Cache->expire;

	alarm(ALARM_INTERVAL);
}
/*--- alarm_handler() ---------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	SIGHUP_HANDLER
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
void
sighup_handler(int dummy)
{
	if (is_master)
	{
		int n;
		for (n = 0; n < multicpu - 1; n++)
			kill(pidlist[n], SIGHUP);
	}
	cache_empty();
	Notice(_("cache emptied due to SIGHUP"));
}
/*--- sighup_handler() --------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	NAMED_CLEANUP
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
void
named_cleanup(int signo)
{
	switch (signo)
	{
		case SIGINT:  Notice(_("interrupted")); break;
		case SIGQUIT: Notice(_("quit")); break;
		case SIGTERM: Notice(_("terminated")); break;
		default: Notice(_("exiting due to signal %d"), signo); break;
	}
	if (is_master)
	{
		int n;
		for (n = 0; n < multicpu - 1; n++)
			kill(pidlist[n], signo);
	}
	time_to_die = 1;
}
/*--- named_cleanup() ---------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	SPAWN_MULTICPU
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static void
spawn_multicpu(void)
{
	int n;

	is_master = 1;
	pidlist = malloc(multicpu * sizeof(pid_t));
	for (n = 1; n < multicpu; n++)
	{
		pid_t pid;

		if ((pid = fork()) < 0)
			Err("fork");
		if (pid > 0)
		{
			pidlist[n-1] = pid;
		}
		else
		{
			is_master = 0;
			return;
		}
	}
}
/*--- spawn_multicpu() --------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	MAIN
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int
main(argc, argv)
	int argc;
	const char *argv[];
{
	int fd, maxfd, rv;
	struct sockaddr_in addr;
	int port;
	fd_set ss, startss;
	struct timeval tv;
	register TASK	*t;

#if MTRACE_ENABLED
	/* mtrace() works only with glibc, AFAIK.  To use, set an environment variable MALLOC_TRACE
		to the name of an output file.  Run the program.  Then see what wasn't freed by running
		"mtrace <program-binary> <output-file>" */
	if (getenv("MALLOC_TRACE"))
	{
		mtrace();
		fprintf(stderr, _("malloc debugging enabled - this binary should not be used in production\n"));
	}
#endif

	bindtextdomain(PACKAGE, LOCALEDIR);						/* Internationalization */
	cmdline(argc, argv);											/* Process command line */

	signal(SIGHUP,  sighup_handler);
	signal(SIGUSR1, sigusr1_handler);
	signal(SIGALRM, alarm_handler);

	signal(SIGINT,  named_cleanup);
	signal(SIGQUIT, named_cleanup);
	signal(SIGABRT, named_cleanup);
	signal(SIGTERM, named_cleanup);

	if (opt_daemon)												/* Move into background if requested */
		become_daemon();
	create_pidfile();												/* Create PID file */
	db_connect();
	Tasks = queue_init();										/* Initialize task queue */
	cache_init();													/* Initialize cache */

	/* Bind to the port */
	if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
		Err("socket");
	port = atoi(conf_get(&Conf, "port"));
	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = htonl(INADDR_ANY);
	addr.sin_port = htons(port);
	if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0)
		Err("bind");
	fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);
	Notice(_("%s ready on port %d"), progname, port);
	time(&Status.start_time);

	/* Spawn a process for each CPU, if multicpu > 1 */
	if ((multicpu = atoi(conf_get(&Conf, "multicpu"))) > 1)
		spawn_multicpu();

	/* Drop permissions */
	if (getgid() == 0 && setgid(perms_gid))
		Err(_("error setting group ID to %d"), perms_gid);
	if (getuid() == 0 && setuid(perms_uid))
		Err(_("error setting user ID to %d"), perms_uid);
	if (!getgid() || !getuid())
		Errx(_("refusing to run as superuser"));

	FD_ZERO(&startss);
	FD_SET(fd, &startss);
	maxfd = fd + 1;
	alarm_handler(SIGALRM);										/* Initialize alarm state */

	/* Main loop: Read connections and process queue */
	for (;;)
	{
		memcpy(&ss, &startss, sizeof(ss));

		/* If the queue is empty and it's `time_to_die', exit. */
		if (!Tasks->size && time_to_die)
		{
			cache_empty();
			return (0);
		}

		/* If there are tasks in the queue, don't block; otherwise, block forever */
		if (Tasks->size)
		{
			tv.tv_sec = tv.tv_usec = 0;
			rv = select(maxfd, &ss, NULL, NULL, &tv);
		}
		else
			rv = select(maxfd, &ss, NULL, NULL, NULL);

		time(&current_time);

		if (rv < 0)
		{
			if (errno == EINTR)
				continue;
			Err("select");
		}
		if (rv > 0)
		{
			if (FD_ISSET(fd, &ss))								/* Read from UDP listener */
				read_udp_query(fd);
		}

		/* Process the queue */
		for (t = Tasks->head; t; t = task_process(t))
			/* DONOTHING */;
	}
	return (0);
}
/*--- main() ------------------------------------------------------------------------------------*/

/* vi:set ts=3: */
/* NEED_PO */
